package com.explorex.infra.puma.gateway.netty.common;

import com.explorex.infra.puma.gateway.netty.ws.consts.ChannelConst;
import com.explorex.infra.puma.gateway.netty.ws.utils.UserUtil;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import io.netty.util.Attribute;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: JacceYang
 * @createTime: 2022/07/14 7:56 下午
 * @description: 服务端链接的和客户端通道
 */
@Slf4j
public class ChannelManager {

    private static final ChannelManager INSTANCE = new ChannelManager();
    private static final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern(ChannelManager.class.getSimpleName() + "-%d").build());
    /**
     * domain user id  mapping to UserSocketSession
     */
    private final static ConcurrentHashMap<String, UserSocketSession> userClientChannelMap = new ConcurrentHashMap<>();
    /**
     * channel id to channel map
     */
    private final static ConcurrentHashMap<String, BaseSocketSession> clientChannelsIdsMap = new ConcurrentHashMap<>();


    private ChannelManager() {
    }

    public static ChannelManager getInstance() {
        return INSTANCE;
    }

    public void bind(Channel channel) {
        if (channel != null) {
            Attribute<UserKey> attr = channel.attr(ChannelConst.USER_KEY);
            bindInternal(attr, channel);
            Attribute<String> topicAttr = channel.attr(ChannelConst.TOPIC_KEY);
            bindTopicInternal(topicAttr, channel);
        }
    }

    private void bindTopicInternal(Attribute<String> attr, Channel channel) {
        if (attr != null && !StringUtil.isNullOrEmpty(attr.get())) {
//            String topic = attr.get();
//            UserSocketSession userSocketSession= userClientChannelMap.getOrDefault(topic, new UserSocketSession(DomainEnum.RED_DOMAIN.getValue(), topic));
//            userSocketSession.addSocket(userKey.getTerminate(),clientChannelsIdsMap.get(channel.id().asLongText()));
        }
    }

    private void bindInternal(Attribute<UserKey> attr, Channel channel) {
        if (attr != null && attr.get() != null) {
            UserKey userKey = attr.get();
            if (clientChannelsIdsMap.get(channel.id().asLongText()) == null || !channel.isActive()) {
                return;
            }
            UserSocketSession userSocketSession = userClientChannelMap.getOrDefault(userKey.domainUser(), new UserSocketSession(userKey.getDomain(), userKey.getUserId()));
            userSocketSession.addSocket(userKey.getTerminal(), clientChannelsIdsMap.get(channel.id().asLongText()));
            userClientChannelMap.put(userKey.domainUser(), userSocketSession);
        }
    }

    public void unbind(Channel channel) {
        if (channel != null) {
            Attribute<UserKey> attr = channel.attr(ChannelConst.USER_KEY);
            unbindInternal(attr, channel);

            Attribute<String> topicAttr = channel.attr(ChannelConst.TOPIC_KEY);
//            unbindInternal(topicAttr, channel);
        }
    }

    private void unbindInternal(Attribute<UserKey> attr, Channel channel) {
        if (attr != null && attr.get() != null) {
            UserKey userKey = attr.get();
            UserSocketSession userSocketSession = userClientChannelMap.get(userKey.domainUser());
            if (userSocketSession != null) {
                Optional<BaseSocketSession> any = userSocketSession.getUserSocketList().stream().filter(ele -> ele.channel() == channel).findAny();
                if (any.isPresent()) {
                    userSocketSession.removeSocket(any.get());
                }
                if (userSocketSession.isEmpty()) {
                    userClientChannelMap.remove(userKey.domainUser());
                }
            }
        }
    }

    public BaseSocketSession getBaseSocketSession(UserKey userKey, String deviceId, String channelId, boolean viewChannelId) {
        UserSocketSession userSocketSession = userClientChannelMap.get(userKey.domainUser());
        if (userSocketSession == null || CollectionUtils.isEmpty(userSocketSession.getUserSocketList())) {
            return null;
        }

//        if (StringUtils.isNotEmpty(channelId)) {
//            if (viewChannelId) {
//                log.info("get session by channelId:{}", channelId);
//            }
//            return userSocketSession.getUserSocketList().stream().filter(socket -> socket.channel().id().asShortText().equals(channelId)).findFirst().orElse(null);
//        }
        return userSocketSession.getUserSocketList().stream().filter(socket -> socket.device().getDeviceId().equals(deviceId)).findFirst().orElse(null);
    }

    public void addVirtualLinkUserLocalCache(BaseSocketSession session, UserKey uid) {
        UserSocketSession userSocketSession = userClientChannelMap.getOrDefault(uid.domainUser(), new UserSocketSession(uid.getDomain(), uid.getUserId()));
        userSocketSession.addSocket(session.getAuthUser().getTerminal(), clientChannelsIdsMap.get(session.channel().id().asLongText()));
        userClientChannelMap.put(uid.domainUser(), userSocketSession);
    }

    public void removeVirtualLinkUserLocalCache(BaseSocketSession session, UserKey virtualLinkUserInfo, String uid) {
        removeLocalCacheByUser(session, virtualLinkUserInfo.domainUser());
        session.getTokenUserInfo().remove(uid);
    }

    public void removeLocalCacheByUser(BaseSocketSession session, String userInfoStr) {
        UserSocketSession userSocketSession = userClientChannelMap.get(userInfoStr);
        if (userSocketSession != null) {
            userSocketSession.removeSocket(session);
            if (CollectionUtils.isEmpty(userSocketSession.getUserSocketList())) {
                userClientChannelMap.remove(userInfoStr);
            }
        }
    }

    public void addSocketSession(Channel channel, BaseSocketSession session) {
        if (channel != null) {
            clientChannelsIdsMap.put(channel.id().asLongText(), session);
        }
    }

    public void removeSocketSession(Channel channel) {
        unbind(channel);
        if (channel != null) {
            clientChannelsIdsMap.remove(channel.id().asLongText());
        }
    }


    public void registerEvent(Channel channel, String uniqueEvent) {
        if (channel == null) {
            return;
        }
        channel.attr(ChannelConst.EVENT_ID_LIST).get().add(uniqueEvent);
    }

    public void cancelRegisterEvent(Channel channel, String uniqueEvent) {
        if (channel == null) {
            return;
        }
        channel.attr(ChannelConst.EVENT_ID_LIST).get().remove(uniqueEvent);
    }

    public BaseSocketSession getSocketSession(Channel channel) {
        return clientChannelsIdsMap.get(channel.id().asLongText());
    }

    public BaseSocketSession getSocketSession(String channelId) {
        if (StringUtils.isBlank(channelId)) {
            return null;
        }
        return clientChannelsIdsMap.get(channelId);
    }

    public List<BaseSocketSession> getAllSocketSession() {
        return Lists.newArrayList(clientChannelsIdsMap.values());
    }

    public List<BaseSocketSession> getUserSocketSession(String userId, String domain, ProtocolEnum protocol, int terminal) {
        List<BaseSocketSession> userSocketSession = getUserSocketSession(userId, domain, protocol);
        return userSocketSession.stream().filter(ele -> ele.terminal() == terminal).collect(Collectors.toList());
    }

    public List<BaseSocketSession> getUserSocketSession(String userId, String domain, ProtocolEnum protocol) {
        List<BaseSocketSession> userSocketSession = getUserSocketSession(userId, domain);
        return userSocketSession.stream().filter(ele -> ele.protocol() == protocol).collect(Collectors.toList());
    }

    public List<BaseSocketSession> getUserSocketSession(String userId, String domain) {
        if (StringUtil.isNullOrEmpty(userId) || StringUtil.isNullOrEmpty(domain)) {
            return Collections.EMPTY_LIST;
        }
        UserSocketSession userSocketSession = userClientChannelMap.get(UserUtil.generateDomainUser(userId, domain));
        if (userSocketSession != null) {
            return userSocketSession.getUserSocketList();
        }
        return Collections.EMPTY_LIST;
    }

}
